﻿#region Header
//
//	Project:		AllegroNET
//	Description:	Silverlight port of Allegro (http://alleg.sourceforge.net/)
//
//	Bitmap routines
//
//	Copyright © 2010 Andrey Gayvoronsky
//
//	Please read the License.txt for details.
//
#endregion

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace AllegroNET
{
	public class BITMAP : IDisposable
	{
		private bool isDisposed = false;
		public bool clip;
		public int cl, cr, ct, cb;
		public int w, h;
		public int x_ofs, y_ofs;

		public int[] line;
		public int[] dat; //pointer for 'source.Pixels' if source is set, otherwise it points to created array
		private WriteableBitmap src = null;
		public WriteableBitmap Source
		{
			get
			{
				return src;
			}
			set
			{
				src = value;

				if (src != null)
					dat = value.Pixels;
			}
		}

		#region IDisposable Members
		~BITMAP()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		protected virtual void Dispose(bool disposing)
		{
			if (!isDisposed)
			{
				if (disposing)
				{
					src = null; // not sure about memory leaks, when WriteableBitmap was rendered via .Render(UIElement). It holds reference for UIElement, so leaks can occur.
					line = null;
					dat = null;
				}

				isDisposed = true;
			}
		}
		#endregion
	}

	public partial class Allegro
	{
		static private BITMAP create_bitmap(int[] buffer, int w, int h)
		{
			if (buffer == null)
				return null;

			BITMAP b = null;

			if (w > 0 && h > 0)
			{
				b = new BITMAP();
				b.dat = buffer;

				b.cl = b.ct = 0;
				b.cr = b.w = w;
				b.cb = b.h = h;
				b.x_ofs = 0;
				b.y_ofs = 0;
				b.clip = true;

				if (h > 0)
				{
					b.line = new int[h];
					b.line[0] = 0;
					for (int i = 1; i < h; i++)
						b.line[i] = b.line[i - 1] + w;
				}
			}

			return b;
		}

		static public BITMAP create_bitmap(WriteableBitmap source)
		{
			if (source == null)
				return null;

			BITMAP b = create_bitmap(source.Pixels, source.PixelWidth, source.PixelHeight);
			b.Source = source;
			return b;
		}

		static public BITMAP create_bitmap(int w, int h, bool zbuffer = false)
		{
			if (zbuffer)
			{
				WriteableBitmap source = new WriteableBitmap(w, h);

				if (source == null)
					return null;

				return create_bitmap(source);
			}


			int[] dat = new int[w * h];
			return create_bitmap(dat, w, h);
		}

		static public BITMAP create_sub_bitmap(BITMAP parent, int x, int y, int w, int h)
		{
			BITMAP b = null;

			if (parent != null && (x >= 0 && y >= 0) && (x < parent.w && y < parent.h) && (w > 0 && h > 0))
			{
				if (x + w > parent.w)
					w = parent.w - x;

				if (y + h > parent.h)
					h = parent.h - y;

				b = new BITMAP();
				b.Source = parent.Source;
				b.dat = parent.dat;

				b.cl = b.ct = 0;
				b.cr = b.w = w;
				b.cb = b.h = h;
				b.x_ofs = x + parent.x_ofs;
				b.y_ofs = y + parent.y_ofs;
				b.clip = true;

				int x_offset = b.x_ofs;

				b.line = new int[h];
				for (int i = 0; i < h; i++)
					b.line[i] = parent.line[y + i] + x_offset;
			}

			return b;
		}

		/* destroy_bitmap:
		 *  Destroys a memory bitmap.
		 */
		static public void destroy_bitmap(BITMAP bmp)
		{
			bmp.Dispose();
		}

		/* is_sub_bitmap:
		 * Tells if a bitmap is a sub bitmap.
		 */
		static public bool is_sub_bitmap(BITMAP bmp)
		{
			return (bmp.x_ofs != 0 || bmp.y_ofs != 0);
		}

		/* bitmap_mask_color:
		 * Returns the mask color of the specified bitmap.
		 * 
		 * Note: just for compatibility. AllegroNet works only with 32 bit images, so mask color is 0xFF00FF
		 */
		static public int bitmap_mask_color(BITMAP bmp)
		{
			return MASK_COLOR_32;
		}

		/* bitmap_color_depth:
		 * Returns the color depth of the specified bitmap.
		 * 
		 * Note: just for compatibility. AllegroNet works only with 32 bit images
		 */
		static public int bitmap_color_depth(BITMAP bmp)
		{
			return 32;
		}

		/* set_clip_rect:
		 *  Sets the two opposite corners of the clipping rectangle to be used when
		 *  drawing to the bitmap. Nothing will be drawn to positions outside of this 
		 *  rectangle. When a new bitmap is created the clipping rectangle will be 
		 *  set to the full area of the bitmap.
		 */
		static public void set_clip_rect(BITMAP bitmap, int x1, int y1, int x2, int y2)
		{
			if (bitmap == null)
				return;

			x2++;
			y2++;

			bitmap.cl = System.Math.Max(0, System.Math.Min(x1, bitmap.w - 1));
			bitmap.ct = System.Math.Max(0, System.Math.Min(y1, bitmap.h - 1));
			bitmap.cr = System.Math.Max(0, System.Math.Min(x2, bitmap.w));
			bitmap.cb = System.Math.Max(0, System.Math.Min(y2, bitmap.h));

			bitmap.clip = true;
		}

		/* get_clip_rect:
		 * Returns the clipping rectangle of a bitmap
		 */
		static public void get_clip_rect(BITMAP bitmap, ref int x1, ref int y1, ref int x2, ref int y2)
		{
			if (bitmap == null)
				return;

			x1 = bitmap.cl;
			y1 = bitmap.ct;
			x2 = bitmap.cr - 1;
			y2 = bitmap.cb - 1;
		}

		/* add_clip_rect:
		 *  Makes the new clipping rectangle the intersection between the given
		 *  rectangle and the current one.
		 */
		static public void add_clip_rect(BITMAP bitmap, int x1, int y1, int x2, int y2)
		{
			int cx1, cy1, cx2, cy2;

			if (bitmap == null)
				return;

			cx1 = cy1 = cx2 = cy2 = 0;

			get_clip_rect(bitmap, ref cx1, ref cy1, ref cx2, ref cy2);

			x1 = System.Math.Max(x1, cx1);
			y1 = System.Math.Max(y1, cy1);

			x2 = System.Math.Min(x2, cx2);
			y2 = System.Math.Min(y2, cy2);

			set_clip_rect(bitmap, x1, y1, x2, y2);
		}

		/* is_inside_bitmap:
		 * Tells if a point is inside a bitmap. If `clip' is true, the function compares the coordinates 
		 * with the clipping rectangle, that is it returns true if the point lies inside the clipping rectangle 
		 * or if clipping is disabled for the bitmap. If `clip' is false, the function compares the coordinates 
		 * with the actual dimensions of the bitmap.
		 */
		static public bool is_inside_bitmap(BITMAP bmp, int x, int y, bool clip)
		{
			if (bmp == null)
				return false;

			if (clip)
			{
				if (bmp.clip)
					// internal clipping is inclusive-exclusive
					return (x >= bmp.cl) && (y >= bmp.ct) && (x < bmp.cr) && (y < bmp.cb);
				else
					return true;
			}
			else
				// bitmap dimensions are always non-negative
				return (uint)x < (uint)bmp.w && (uint)y < (int)bmp.h;
		}

		/* set_clip_state:
		 * Turns on or off the clipping of a bitmap.
		 */
		static public void set_clip_state(BITMAP bitmap, bool state)
		{
			if (bitmap == null)
				return;

			bitmap.clip = state;
		}

		/* get_clip_state:
		 * Tells if clipping is on for a bitmap.
		 */
		static public bool get_clip_state(BITMAP bitmap)
		{
			if (bitmap == null)
				return false;

			return bitmap.clip;
		}

		/* blit:
		 *  Copies an area of the source bitmap to the destination bitmap. s_x and 
		 *  s_y give the top left corner of the area of the source bitmap to copy, 
		 *  and d_x and d_y give the position in the destination bitmap. w and h 
		 *  give the size of the area to blit. This routine respects the clipping 
		 *  rectangle of the destination bitmap, and will work correctly even when 
		 *  the two memory areas overlap (ie. src and dest are the same). 
		 */
		static public void blit(BITMAP src, BITMAP dest, int s_x, int s_y, int d_x, int d_y, int w, int h)
		{
			// check for ridiculous cases 
			if ((s_x >= src.w) || (s_y >= src.h) || (d_x >= dest.cr) || (d_y >= dest.cb))
				return;

			// clip src left 
			if (s_x < 0)
			{
				w += s_x;
				d_x -= s_x;
				s_x = 0;
			}

			// clip src top 
			if (s_y < 0)
			{
				h += s_y;
				d_y -= s_y;
				s_y = 0;
			}

			// clip src right 
			if (s_x + w > src.w)
				w = src.w - s_x;

			// clip src bottom 
			if (s_y + h > src.h)
				h = src.h - s_y;

			// clip dest left 
			if (d_x < dest.cl)
			{
				d_x -= dest.cl;
				w += d_x;
				s_x -= d_x;
				d_x = dest.cl;
			}

			// clip dest top 
			if (d_y < dest.ct)
			{
				d_y -= dest.ct;
				h += d_y;
				s_y -= d_y;
				d_y = dest.ct;
			}

			// clip dest right 
			if (d_x + w > dest.cr)
				w = dest.cr - d_x;

			// clip dest bottom 
			if (d_y + h > dest.cb)
				h = dest.cb - d_y;

			// bottle out if zero size 
			if ((w <= 0) || (h <= 0))
				return;

			int size_of_rgb = sizeof(int);
			int y = 0;
			int len = w * size_of_rgb;

			s_x *= size_of_rgb;
			d_x *= size_of_rgb;

			s_y = src.line[s_y] * size_of_rgb + s_x;
			d_y = dest.line[d_y] * size_of_rgb + d_x;

			int step_s_y = src.w * size_of_rgb;
			int step_d_y = dest.w * size_of_rgb;

			while (y < h)
			{
				Buffer.BlockCopy(src.dat, s_y, dest.dat, d_y, len);

				s_y += step_s_y;
				d_y += step_d_y;

				y++;
			}
		}

		static private void _al_stretch_blit(BITMAP src, BITMAP dst, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, bool masked)
		{
			int y;				// current dst y 
			int yc;				// y counter 
			int sxofs, dxofs;	// start offsets 
			int syinc;			// amount to increment src y each time 
			int ycdec;			// amount to decrement counter by, increase sy when this reaches 0 
			int ycinc;			// amount to increment counter by when it reaches 0 
			int size = 0;		// pixel size 
			int dxbeg, dxend;	// clipping information 
			int dybeg, dyend;
			int i;

			if (src == null || dst == null)
				return;

			if ((sw <= 0) || (sh <= 0) || (dw <= 0) || (dh <= 0))
				return;

			size = 1;// size_of_rgb;

			if (dst.clip)
			{
				dybeg = ((dy > dst.ct) ? dy : dst.ct);
				dyend = (((dy + dh) < dst.cb) ? (dy + dh) : dst.cb);
				if (dybeg >= dyend)
					return;

				dxbeg = ((dx > dst.cl) ? dx : dst.cl);
				dxend = (((dx + dw) < dst.cr) ? (dx + dw) : dst.cr);
				if (dxbeg >= dxend)
					return;
			}
			else
			{
				dxbeg = dx;
				dxend = dx + dw;
				dybeg = dy;
				dyend = dy + dh;
			}

			syinc = sh / dh;
			ycdec = sh - (syinc * dh);
			ycinc = dh - ycdec;
			yc = ycinc;
			sxofs = sx * size;
			dxofs = dx * size;

			int sxinc = sw / dw * size;				// amount to increment src x every time 
			int xcdec = sw - ((sw / dw) * dw);		// amount to decrement counter by, increase sptr when this reaches 0 
			int xcinc = dw - xcdec;					// amount to increment counter by when it reaches 0 
			int linesize = (dxend - dxbeg) * size;	// size of a whole row of pixels 
			int xcstart = xcinc;					//x counter start 

			// get start state (clip)
			for (i = 0; i < dxbeg - dx; i++, sxofs += sxinc)
			{
				if (xcstart <= 0)
				{
					xcstart += xcinc;
					sxofs += size;
				}
				else
					xcstart -= xcdec;
			}

			dxofs += i * size;

			// skip clipped lines
			for (y = dy; y < dybeg; y++, sy += syinc)
			{
				if (yc <= 0)
				{
					sy++;
					yc += ycinc;
				}
				else
					yc -= ycdec;
			}

			// Stretch it 
			for (; y < dyend; y++, sy += syinc)
			{
				int dptr = y * dst.w + dxofs;
				int sptr = sy * src.w + sxofs;

				int xc = xcstart;
				int dend = dptr + linesize;

				if (masked)
				{
					for (; dptr < dend; dptr += size, sptr += sxinc)
					{
						int color = src.dat[sptr];

						if ((color & 0x00FFFFFF) != MASK_COLOR_32)
							dst.dat[dptr] = color;

						if (xc <= 0)
						{
							sptr += size;
							xc += xcinc;
						}
						else
							xc -= xcdec;
					}
				}
				else
				{
					for (; dptr < dend; dptr += size, sptr += sxinc)
					{
						dst.dat[dptr] = src.dat[sptr];

						if (xc <= 0)
						{
							sptr += size;
							xc += xcinc;
						}
						else
							xc -= xcdec;
					}
				}

				if (yc <= 0)
				{
					sy++;
					yc += ycinc;
				}
				else
					yc -= ycdec;
			}
		}

		/* stretch_blit:
		 *  Opaque bitmap scaling function.
		 */
		static public void stretch_blit(BITMAP src, BITMAP dst, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh)
		{
			_al_stretch_blit(src, dst, sx, sy, sw, sh, dx, dy, dw, dh, false);
		}

		/* masked_blit:
		 *  Version of blit() that skips zero pixels.
		 */
		static public void masked_blit(BITMAP src, BITMAP dest, int s_x, int s_y, int d_x, int d_y, int w, int h)
		{
			if (src == null || dest == null)
				return;

			int x, y, c;

			// check for ridiculous cases 
			if ((s_x >= src.w) || (s_y >= src.h) || (d_x >= dest.cr) || (d_y >= dest.cb))
				return;

			// clip src left 
			if (s_x < 0)
			{
				w += s_x;
				d_x -= s_x;
				s_x = 0;
			}

			// clip src top 
			if (s_y < 0)
			{
				h += s_y;
				d_y -= s_y;
				s_y = 0;
			}

			// clip src right 
			if (s_x + w > src.w)
				w = src.w - s_x;

			// clip src bottom 
			if (s_y + h > src.h)
				h = src.h - s_y;

			// clip dest left 
			if (d_x < dest.cl)
			{
				d_x -= dest.cl;
				w += d_x;
				s_x -= d_x;
				d_x = dest.cl;
			}

			// clip dest top 
			if (d_y < dest.ct)
			{
				d_y -= dest.ct;
				h += d_y;
				s_y -= d_y;
				d_y = dest.ct;
			}

			// clip dest right 
			if (d_x + w > dest.cr)
				w = dest.cr - d_x;

			// clip dest bottom 
			if (d_y + h > dest.cb)
				h = dest.cb - d_y;

			// bottle out if zero size 
			if ((w <= 0) || (h <= 0))
				return;

			for (y = 0; y < h; y++)
			{
				int s_ptr = src.line[s_y + y] + s_x;
				int d_ptr = dest.line[d_y + y] + d_x;

				for (x = w - 1; x >= 0; s_ptr++, d_ptr++, x--)
				{
					c = src.dat[s_ptr];

					if ((c & 0x00FFFFFF) != MASK_COLOR_32)
					{
						dest.dat[d_ptr] = c;
					}
				}
			}
		}

		/* masked_stretch_blit:
		 *  Masked bitmap scaling function.
		 */
		static public void masked_stretch_blit(BITMAP src, BITMAP dst, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh)
		{
			_al_stretch_blit(src, dst, sx, sy, sw, sh, dx, dy, dw, dh, true);
		}

		/* stretch_sprite:
		 *  Masked version of stretch_blit().
		 */
		static public void stretch_sprite(BITMAP dst, BITMAP src, int x, int y, int w, int h)
		{
			_al_stretch_blit(src, dst, 0, 0, src.w, src.h, x, y, w, h, true);
		}

		/* draw_sprite:
		 *  Draws a sprite onto a bitmap at the specified x, y position,
		 *  using a masked drawing mode where zero pixels are not output.
		 */
		static public void draw_sprite(BITMAP bmp, BITMAP sprite, int dx, int dy)
		{
			if (bmp == null || sprite == null)
				return;

			masked_blit(sprite, bmp, 0, 0, dx, dy, sprite.w, sprite.h);
		}

		/* draw_character:
		 *  Draws a copy of the sprite bitmap onto the destination bitmap at the specified 
		 *  position, drawing transparent pixels in the background color (or skipping them 
		 *  if the background color is -1) and setting all other pixels to the specified color. 
		 */
		static public void draw_character_ex(BITMAP bmp, BITMAP sprite, int dx, int dy, int color, int bg)
		{
			if (bmp == null || sprite == null)
				return;

			int x, y, w, h, c;
			int dxbeg, dybeg;
			int sxbeg, sybeg;

			if (bmp.clip)
			{
				int tmp;

				tmp = bmp.cl - dx;
				sxbeg = ((tmp < 0) ? 0 : tmp);
				dxbeg = sxbeg + dx;

				tmp = bmp.cr - dx;
				w = ((tmp > sprite.w) ? sprite.w : tmp) - sxbeg;

				if (w <= 0)
					return;

				tmp = bmp.ct - dy;
				sybeg = ((tmp < 0) ? 0 : tmp);
				dybeg = sybeg + dy;

				tmp = bmp.cb - dy;
				h = ((tmp > sprite.h) ? sprite.h : tmp) - sybeg;

				if (h <= 0)
					return;
			}
			else
			{
				w = sprite.w;
				h = sprite.h;
				sxbeg = 0;
				sybeg = 0;
				dxbeg = dx;
				dybeg = dy;
			}

			if (bg < 0) //Masked character.
			{
				for (y = 0; y < h; y++)
				{
					int s_ptr = sprite.line[sybeg + y] + sxbeg;
					int d_ptr = bmp.line[dybeg + y] + dxbeg;

					for (x = w - 1; x >= 0; s_ptr++, d_ptr++, x--)
					{
						c = sprite.dat[s_ptr] & 0x00ffffff; //remove alpha-channel

						if (c != MASK_COLOR_32)
							bmp.dat[d_ptr] = color;
					}
				}
			}
			else // Opaque character.  
			{
				for (y = 0; y < h; y++)
				{
					int s_ptr = sprite.line[sybeg + y] + sxbeg;
					int d_ptr = bmp.line[dybeg + y] + dxbeg;

					for (x = w - 1; x >= 0; s_ptr++, d_ptr++, x--)
					{
						c = sprite.dat[s_ptr] & 0x00ffffff; //remove alpha-channel
						bmp.dat[d_ptr] = (c != MASK_COLOR_32) ? color : bg;
					}
				}
			}

		}

		/* rotate_sprite:
		 *  Draws a sprite image onto a bitmap at the specified position, rotating
		 *  it by the specified angle.
		 */
		static public void rotate_sprite(BITMAP bmp, BITMAP sprite, int x, int y, double angle)
		{
			_rotate_sprite(bmp, sprite, x, y, sprite.w / 2, sprite.h / 2, angle, 1.0);
		}

		/* rotate_scaled_sprite:
		 *  Draws a scaled sprite image onto a bitmap at the specified position, rotating
		 *  it by the specified angle. 
		 */
		static public void rotate_scaled_sprite(BITMAP bmp, BITMAP sprite, int x, int y, double angle, double scale)
		{
			_rotate_sprite(bmp, sprite, x, y, sprite.w / 2, sprite.h / 2, angle, scale);
		}

		/* pivot_sprite:
		 *  Rotates a sprite around the specified pivot centre point.
		 */
		static public void pivot_sprite(BITMAP bmp, BITMAP sprite, int x, int y, int cx, int cy, double angle)
		{
			_rotate_sprite(bmp, sprite, x, y, cx, cy, angle, 1.0);
		}

		/* pivot_scaled_sprite:
		 *  Rotates and scales a sprite around the specified pivot centre point.
		 */
		static public void pivot_scaled_sprite(BITMAP bmp, BITMAP sprite, int x, int y, int cx, int cy, double angle, double scale)
		{
			_rotate_sprite(bmp, sprite, x, y, cx, cy, angle, scale);
		}

		/* _rotate_sprite:
		 * Workhorse function for all types of rotation
		 */
		static private void _rotate_sprite(BITMAP bmp, BITMAP sprite, int dx, int dy, int cx, int cy, double angle, double scale)
		{
			double d2rad = (System.Math.PI / 180);
			double cosa = System.Math.Cos(-angle * d2rad);
			double sina = System.Math.Sin(-angle * d2rad);

			double delta_x_col = cosa * (1.0 / scale);
			double delta_y_col = sina * (1.0 / scale);
			double delta_x_row = delta_y_col;
			double delta_y_row = -delta_x_col;

			double w_dash = scale * sprite.w * System.Math.Abs(cosa) + scale * sprite.h * System.Math.Abs(sina);
			double h_dash = scale * sprite.h * System.Math.Abs(cosa) + scale * sprite.w * System.Math.Abs(sina);

			int rw_dash = (int)w_dash;
			int rh_dash = (int)h_dash;

			int ofs_x_dash = (int)(dx - w_dash / 2 + sprite.w * scale / 2);
			int ofs_y_dash = (int)(dy - h_dash / 2 + sprite.h * scale / 2);

			double dst_cx = w_dash / 2;
			double dst_cy = h_dash / 2;

			double start_x = cx - (dst_cx * delta_y_col + dst_cy * delta_x_col);
			double start_y = cy - (dst_cx * delta_y_row + dst_cy * delta_x_row);

			int start_x_dash = 0;
			int start_y_dash = 0;

			//need make some clipping
			if (bmp.clip)
			{
				//clip left
				if (ofs_x_dash < bmp.cl)
				{
					start_x_dash -= ofs_x_dash;
					start_x += delta_x_row * start_x_dash;
					start_y += delta_y_row * start_x_dash;
				}

				//clip top
				if (ofs_y_dash < bmp.ct)
				{
					start_y_dash -= ofs_y_dash;
					start_x += delta_x_col * start_y_dash;
					start_y += delta_y_col * start_y_dash;
				}

				//clip right
				if (ofs_x_dash + rw_dash > bmp.cr)
					rw_dash = bmp.cr - ofs_x_dash;

				//clipt bottom
				if (ofs_y_dash + rh_dash > bmp.cb)
					rh_dash = bmp.cb - ofs_y_dash;

			}

			double x, y;

			int dst_ptr,c, rx, ry;

			int w = sprite.w;
			int h = sprite.h;
			
			double row_x = start_x;
			double row_y = start_y;

			for (int y_dash = start_y_dash; y_dash < rh_dash; y_dash++)
			{
				x = row_x;
				y = row_y;
				dst_ptr = bmp.line[y_dash + ofs_y_dash];

				for (int x_dash = start_x_dash; x_dash < rw_dash; x_dash++)
				{
					rx = (int)System.Math.Round(x);
					ry = (int)System.Math.Round(y);

					if ((rx >= 0 && ry >= 0) && (rx < w && ry < h))
					{
						c = sprite.dat[sprite.line[ry] + rx];

						if ((c & 0x00ffffff) != MASK_COLOR_32)
							bmp.dat[dst_ptr + x_dash + ofs_x_dash] = c;
					}

					x += delta_x_row;
					y += delta_y_row;
				}

				row_x += delta_x_col;
				row_y += delta_y_col;
			}
		}
	}
}
